import os
import re
import cv2
import time
import copy
import pickle
import struct
import random
import logging
import logging.handlers
import requests
# from chrt_logger import logger
from PIL import Image
from urllib.parse import urlencode

'''
日志模块
'''
LOG_FILENAME = 'chrt.log'
logger = logging.getLogger()


def set_logger():
    logger.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(process)d-%(threadName)s - '
                                  '%(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s')
    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)
    file_handler = logging.handlers.RotatingFileHandler(
        LOG_FILENAME, maxBytes=10485760, backupCount=5, encoding="utf-8")
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

set_logger()

NAME = '王维'
CARDID = 420115198804030032

headers = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
           "Connection": "keep-alive",}

class StudySession:
    """
    Session相关操作
    """
    def __init__(self):
        self.cookies_dir_path = "./studycookies/"
        self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36'
        self.session = self._init_session()

    def _init_session(self):
        session = requests.session()
        session.headers = self.get_headers()
        return session

    def get_headers(self):
        return {"User-Agent": self.user_agent,
                "Connection": "keep-alive",
                'X-Requested-With': 'XMLHttpRequest'}

    def get_user_agent(self):
        return self.user_agent

    def get_session(self):
        """
        获取当前Session
        :return:
        """
        return self.session

    def get_cookies(self):
        """
        获取当前Cookies
        :return:
        """
        return self.get_session().cookies

    def set_cookies(self, cookies):
        self.session.cookies.update(cookies)

    def load_cookies_from_local(self):
        """
        从本地加载Cookie
        :return:
        """
        cookies_file = ''
        if not os.path.exists(self.cookies_dir_path):
            return False
        for name in os.listdir(self.cookies_dir_path):
            if name.endswith(".cookies"):
                cookies_file = '{}{}'.format(self.cookies_dir_path, name)
                break
        if cookies_file == '':
            return False
        with open(cookies_file, 'rb') as f:
            local_cookies = pickle.load(f)
        self.set_cookies(local_cookies)

    def save_cookies_to_local(self, cookie_file_name):
        """
        保存Cookie到本地
        :param cookie_file_name: 存放Cookie的文件名称
        :return:
        """
        cookies_file = '{}{}.cookies'.format(self.cookies_dir_path, cookie_file_name)
        directory = os.path.dirname(cookies_file)
        if not os.path.exists(directory):
            os.makedirs(directory)
        with open(cookies_file, 'wb') as f:
            pickle.dump(self.get_cookies(), f)
            
class login:
    '''
    登录
    '''
    def __init__(self, study_session:StudySession):
        self.study_session = study_session
        self.session = self.study_session.get_session()
        self.is_login = False
        self.refresh_login_status()
        
    def refresh_login_status(self):
        """
        刷新是否登录状态
        :return:
        """
        self.is_login = self._validate_cookies()
        
    def _validate_cookies(self):
        """
        验证cookies是否有效（是否登陆）
        通过访问个人中心列表页进行判断：若未登录，将会重定向到登陆页面。
        :return: cookies是否有效 True/False
        """
        url = 'http://www.sptce.cn/uc/index'
        try:
            resp = self.session.get(url=url,allow_redirects=False)
            if resp.status_code == requests.codes.OK:
                return True
        except Exception as e:
            logger.error("验证cookies是否有效发生异常", e)
        return False
    
    def login_by_username(self):
        codeurl = 'http://www.sptce.cn/ran/ajax/random'
        # code_html = session.get(self.login_url,headers=headers,timeout=10).content.decode('utf-8')
        valcode = session.get(codeurl)
        with open('code.jpg','wb') as fp:
            fp.write(valcode.content)
        #打开并显示图片
        img=Image.open('code.jpg')
        img.show()
        params = {'idCardNo': CARDID,
                  'realName': NAME,
                  }
        params['randomcode'] = input('输入验证码：')
        r = session.post("http://www.sptce.cn/user/ajax/getloginUser", params=params, headers=headers)
        self.refresh_login_status()
        return session
        
class ChrtCourseInfo:
    '''
    上海市专业技术人员继续教育在线学习平台自动学习
    https://web.chinahrt.com/
    '''
    def __init__(self,session,headers,cookies):
        self.session = session
        self.headers = headers
        self.cookies = cookies
        
        
    def check_json_info(self,resp_json,resp_text,name,f_strs):
        '''
        检查返回的json文件是否包含指定关键字的信息
        '''
        try:
            for f_str in f_strs:
                resp_json = resp_json[f_str]
            return False
        except Exception as e:
            logger.info('{}获取失败，<{}>数据未返回，返回信息:{}，{}'.format(name,f_str,resp_text[0:128],e))
            return True
        
    def get_trainplan_list(self):
        trainplan_url = 'https://web.chinahrt.com/vueapi/trainplan/list'
        params = {'isFinish': 0,
                  'pageSize': 5,
                  'curPage': 1,
                  'trainplanType':'',
                  }
        trainplan_resp = self.session.get(trainplan_url,params=params,headers=self.headers,cookies=self.cookies)
        trainplan_json = trainplan_resp.json()
        # self.check_sucess(trainplan_info)
        if self.check_json_info(trainplan_json,trainplan_resp.text,'课程视频播放界面',['data','listData']):
            return False 
        return trainplan_json['data']['listData']

    def get_all_courses(self,trainplanId,platformId,learnFinish=0):
        '''
        获取所有的课程信息
        learnFinish = 0（未学完课程），1（学完课程），大于1或空值（全部课程）
        trainplanId,platformId均取值trainplan_list界面返回的值
        '''
        max_iter = 20
        i = 1 
        all_course = []
        while i < max_iter:
            my_course_url = 'https://web.chinahrt.com/vueapi/trainplanCourseHandle/selected_course'
            params = {'trainplanId':trainplanId,
                      'platformId':platformId,
                      'curPage':i,
                      'pageSize': 12,
                      'selectCourseClassId': '',
                      'classType':'',
                      'learnFinish':learnFinish,
                      'sortField':1,
                      'sortType':'DESC',
                      }
            my_course_resp = self.session.get(my_course_url,params=params,headers=self.headers,cookies=self.cookies)
            my_course_info = my_course_resp.json()
            if len(my_course_info['data']['courseStudyList']) == 0:
                break
            all_course.append(my_course_info)
            i += 1
        if len(all_course) > 1:
            my_course_info = all_course[0]
            for course in all_course[:-1]:
                my_course_info['data']['courseStudyList'] += course['data']['courseStudyList']
            return my_course_info['data']['courseStudyList']
        elif len(all_course) == 1:
            return all_course[0]['data']['courseStudyList']
        return all_course
            
    def get_play_info(self,courseId,sectionId,trainplanId,playType='VUE'):
        '获取 recordId updateRedisMap studyCode'
        video_play_url = 'https://web.chinahrt.com/vueapi/course/playVideo'
        params = {'courseId':courseId,
                  'sectionId':sectionId,
                  'trainplanId':trainplanId,
                  'playType':playType,
                  }
        video_play_resp = self.session.get(video_play_url,params=params,headers=self.headers,cookies=self.cookies)
        video_play_json = video_play_resp.json()
        if self.check_json_info(video_play_json,video_play_resp.text,'课程视频播放界面',['data','playUrl']):
            return False,False,False,False 
        play_url = video_play_resp.json()['data']['playUrl']
        play_cookies = {}
        play_cookies['JSESSIONID'] = self.cookies['JSESSIONID']
        play_session = requests.session()
        player_resp = play_session.get(play_url,headers=self.headers,cookies=play_cookies)
        text = player_resp.content.decode(player_resp.encoding)
        with open('code.txt', "w", encoding='utf-8') as f:
            f.write(text)
            f.close()
        # text.save('code.txt')
        try:
            recordId = re.compile("attrset.recordId = \"(.*?)\"").findall(text)[0]
            updateRedisMap = re.compile("attrset.updateRedisMap = \"(.*?)\"").findall(text)[0]
            studyCode = re.compile("attrset.studyCode = \"(.*?)\"").findall(text)[0]
            # videoTransUrl = re.compile("\"videoTransUrl\":\"(.*?)\"").findall(play_text)
            return play_url,recordId,updateRedisMap,studyCode
        except Exception as e:
            logger.info('课程视频播放界面获取失败，视频观看认证关键数据未发现，返回信息:{}，{}'.format(text[0:128],e))
            return False,False,False,False 
    
    def takeRecord_post(self,play_url,recordId,updateRedisMap,studyCode,sectionId,signId,businessId,time):
        '''
        向服务器post播放记录
        '''
        takeRecord_url = 'https://videoadmin.chinahrt.com/videoPlay/takeRecord'
        takeRecord = {}
        takeRecord['JSESSIONID'] = self.cookies['JSESSIONID']
        params = {'studyCode': studyCode,
                  'updateRedisMap':updateRedisMap,
                  'recordId':recordId,
                  'sectionId':sectionId,
                  'signId':signId,
                  'businessId':businessId,
                  }
        if isinstance(time,bool):
            params['isEnd'] = time
        else:
            params['time'] = time
        takeRecord_headers = self.headers
        takeRecord_headers['Referer'] = play_url
        takeRecord_resp = self.session.post(takeRecord_url,params=params,headers=takeRecord_headers,cookies=takeRecord)
        return takeRecord_resp.json()['status']
    
    def get_course_info(self,trainplanId,platformId,courseId):
        course_url = 'https://web.chinahrt.com/vueapi/course/courseDetail'
        params = {'courseId':courseId,
                  'trainplanId':trainplanId,
                  'platformId':platformId,
                  }
        course_resp = self.session.get(course_url,params=params,headers=self.headers,cookies=self.cookies)
        course_resp_json = course_resp.json()
        if self.check_json_info(course_resp_json,course_resp.text,"课程详细章节信息页面",['data','course','chapter_list']):
            return False
        section_dict = course_resp_json['data']['course']['chapter_list'][0]
        if self.check_json_info(section_dict,course_resp.text,"课程详细章节信息页面",['section_list']):
            return False
        return section_dict['section_list']
    
    def str2sec(self,string):
        h,m,s = string.split(':')
        return int(h)*3600+int(m)*60+int(s)
    
    def play_course(self,trainplanId,platformId,courseId):
        '''
        视频学习验证机制（20210325）
        每隔30s向'https://videoadmin.chinahrt.com/videoPlay/takeRecord'发送get请求验证视频播放
        参数为recordId,updateRedisMap,studyCode,sectionId,signId,businessId,time(当前已播放时间)
        '''
        # 109#41930878996c4be7b26e63d2063158c7#e45bde79-dc6d-4f18-9b52-008bc3708b6d
        section_list = self.get_course_info(trainplanId,platformId,courseId)
        for section_info in section_list:
            if section_info['study_status'] == "已学完":
                continue
            sectionId = section_info['id']
            play_url,recordId,updateRedisMap,studyCode = self.get_play_info(courseId,sectionId,trainplanId)
            totalTime = float(section_info['total_time'])
            current_time = self.str2sec(section_info['studyTimeHHmmss'])
            if current_time >= totalTime:
                continue
            signId = re.compile("signId=(.*?)&").findall(play_url)[0]
            signId = signId.replace('%','#')
            businessId = re.compile("business_id=(.*?)&").findall(play_url)[0]
            logger.info('开始学习《{}》......'.format(section_info['name']))
            while current_time < totalTime:
                sleep_time = 30+random.random()
                time.sleep(2)
                current_time += sleep_time
                if current_time >= totalTime:
                    current_time = totalTime
                    statu = self.takeRecord_post(play_url,recordId,updateRedisMap,studyCode,sectionId,signId,businessId,True)
                else:
                    statu = self.takeRecord_post(play_url,recordId,updateRedisMap,studyCode,sectionId,signId,businessId,current_time)
                if statu == '0' :
                    logger.info("\r播放进度为:%d%%" %(current_time*100/totalTime), end='')
            logger.info('完成《{}》的学习'.format(section_info['name']))
        
    def take_exam(self,examId):
        '''
        参加考试直至合格
        '''
        paper = self.get_exam_paper(examId)
        paperId = paper['id']
        p_a = PaperAnswer(paper)
        answer = p_a.get_my_answer()
        i = 0
        while True and i < 20:
            # time.sleep(100+100*random.random())
            recordId = self.submit_answers(paperId,answer)
            exam_result = self.get_exam_result(recordId)
            if exam_result['exam_score'] > 60:
                logger.info('课程<{}>考试合格，分数为{:d}'.format(paper['exam_name'],exam_result['exam_score']))
                break
            p_a.update_all_answer(exam_result)
            answer = p_a.get_my_answer()
            i += 1
        
    def get_all_examinfo(self):
        my_exam_url = 'https://web.chinahrt.com/vueapi/exam/examList'
        max_iter = 20
        i = 1 
        all_exam = []
        while i < max_iter:
            # my_course_url = 'https://web.chinahrt.com/vueapi/trainplanCourseHandle/selected_course'
            params = {'isHistory': 0,
                      'curPage': i,
                      'pageSize': 10,
                      'name': '',
                      'order_sequence': 'time',
                      'order_type': 'up',
                      'trainplanName': '',
                      'examResult': '',
                      }
            my_exam_resp = self.session.get(my_exam_url,params=params,headers=self.headers,cookies=self.cookies)
            my_exam_info = my_exam_resp.json()
            if len(my_exam_info['data']['data']) == 0:
                break
            all_exam += my_exam_info['data']['data']
            i += 1
        return all_exam
    
    def get_exam_result(self,recordId):
        '''
        获取系统考试得分信息
        '''
        view_exam_result_url = 'https://web.chinahrt.com/vueapi/exam/view_exam_result'
        params = {'recordId': recordId}
        view_exam_result_resp = self.session.get(view_exam_result_url,params=params,headers=self.headers,cookies=self.cookies)
        view_exam_result_json = view_exam_result_resp.json()
        self.check_json_info(view_exam_result_json,view_exam_result_resp.text,"考试结果",['data','pager'])
        return view_exam_result_json['data']['pager']
    
    def submit_answers(self,paperId,answers):
        '''
        提交答案,需要向两个地址提交post信息paperId,answers
        'vueapi/exam/submit_exam_all_answer','https://web.chinahrt.com/vueapi/exam/submit_exam_answer'
        参数
        paperId：试卷id，在获取试卷信息的api中获取（vueapi/exam/go_exam'）
        '''
        # judge_exam_url = 'https://web.chinahrt.com/vueapi/exam/judge_go_exam'
        submit_answers_url = 'https://web.chinahrt.com/vueapi/exam/submit_exam_all_answer'
        # submit_answers_url2 = 'https://web.chinahrt.com/vueapi/exam/submit_exam_answer'
        # view_exam_result_url = 'https://web.chinahrt.com/vueapi/exam/view_exam_result'
        params = {'paperId': paperId,
                  'answers':answers,}
        submit_answers_url += '?'+urlencode(params)
        # submit_answers_url2 +=
        # e_a = urllib.urlencode(answers)
        # self.session.post(judge_exam_url,paperId=paperId,headers=self.headers,cookies=self.cookies)
        submit_answers_resp = self.session.post(submit_answers_url,headers=self.headers,cookies=self.cookies)
        submit_answers_json = submit_answers_resp.json()
        self.check_json_info(submit_answers_json,submit_answers_resp.text,"考试结果",['data','record_id'])
        # time.sleep(random.random()*10)
        # self.session.post(submit_answers_url2,params=params,headers=self.headers,cookies=self.cookies)
        return submit_answers_json['data']['record_id']
    
    def get_exam_paper(self,examId):
        '''
        获取考试题目信息
        '''
        paper_url = 'https://web.chinahrt.com/vueapi/exam/go_exam'
        params = {'examId': examId}
        paper_resp = self.session.get(paper_url,params=params,headers=self.headers,cookies=self.cookies)
        paper_json = paper_resp.json()
        self.check_json_info(paper_json,paper_resp.text,"课程考试",['data','pager'])
        return paper_json['data']['pager']
    
    def pass_all_exam(self):
        all_exam = self.get_all_examinfo()
        if len(all_exam) == 0:
            logger.info('完成本账号没有未完成的考试')
        for exam in all_exam:
            examId = exam['id']
            if exam['noFinishCourseCount'] > 0:
                logger.info('课程<{}>未完成视频学习'.format(exam['name']))
                continue
            if exam['exam_score'] > 60.0 :
                continue
            logger.info('课程<{}>开始自动考试'.format(exam['name'])) 
            try:
                self.take_exam(examId)
                logger.info('课程<{}>考试完成'.format(exam['name']))
            except:
                continue
        logger.info('完成本账号所有课程考试')
            
    def study_all_courses(self,exam=False):
        '''
        搜索未完成的课程并学习
        '''
        
        for train_plan in self.get_trainplan_list():
            trainplanId = train_plan['id']
            platformId = train_plan['platform_id']
            trainplan_name = train_plan['name']
            all_courses = self.get_all_courses(trainplanId,platformId)
            if len(all_courses)==0:
                logger.info('{}未发现需要学习的课程'.format(trainplan_name))
                continue
            for course in all_courses:
                courseId = course['courseId']
                courseName = course['courseName']
                logger.info('开始学习的课程《{}》......'.format(courseName))
                # try:
                self.play_course(trainplanId,platformId,courseId)
                # except:
                #     continue
                logger.info('完成课程《{}》的学习！'.format(courseName))
                if exam:
                    self.take_exam(courseId)
            logger.info('{}中完成所有课程学习！'.format(trainplan_name))
        logger.info('完成本账号所有购入课程学习')

    def get_video_timeinfo(self,videoTransUrl):
        "获取视频时长"
        video_resp = requests.get(videoTransUrl, stream=True)
        for data in video_resp.iter_content(chunk_size=512):
            if data.find(b'mvhd') > 0:
                index = data.find(b'mvhd') + 4
                time_scale = struct.unpack('>I', data[index + 13:index + 13 + 4])
                durations = struct.unpack('>I', data[index + 13 + 4:index + 13 + 4 + 4])
                duration = durations[0] / time_scale[0]
                break
        return duration
    
def get_cookie_dict():
    cookie_dict = {}
    cookies = 'chinahrtorg=420115198804030032%2Cjxaa035544; chinahrtPlatformId=109; chinahrtOrgId=109_5554; Hm_lvt_314ca85a35846e1726e5bccc40a0fe6d=1617010927; JSESSIONID=006392ADBF38C86BB847647C33601D01; Hm_lpvt_314ca85a35846e1726e5bccc40a0fe6d=1617119421'
    for kv in cookies.split('; '):
        # kv: 'td_cookie=184xxx'
        key = kv.split('=')[0]
        value = kv.split('=')[1]
        cookie_dict[key] = value
    return cookie_dict

class PaperAnswer:
    '''
    考试答案
    随机出题
    根据每次考试的结果更新答案库
    '''
    def __init__(self,pager):
        self.pager = pager
        self.judge_list = pager['judge_list']
        self.multi_list = pager['multi_list']
        self.single_list = pager['single_list']
        self.all_answers = self.build_all_answers()
        
    def __get_question_info(self,question,answers):
        new_item = {"id":'',
                    "mark":'',
                    'answer':'',
                    }
        for key in question.keys():
            if key in ["id","mark"]:
                new_item[key] = question[key]
            elif key == 'my_answer':
                new_item['answer'] = answers
            else:
                continue
            # del question[key]
        return new_item
        
    def build_all_answers(self):
        '''
        初始化答案
        '''
        all_answers = []
        for single in self.single_list:
            item = self.__get_question_info(single,['A','B','C','D'])
            all_answers.append(item)
        for multi in self.multi_list:   
            item = self.__get_question_info(multi,['AB','AC','AD','BC','BD','CD','ABC','ABD','ACD','BCD','ABCD'])
            all_answers.append(item)
        for judge in self.judge_list:    
            item = self.__get_question_info(judge,['0','1'])
            all_answers.append(item)
        return all_answers
    
    def get_my_answer(self):
        '''
        随机猜一组答案
        '''
        my_answer = []
        all_answers = copy.deepcopy(self.all_answers)
        for answer in all_answers:
            answer['answer'] = random.choice(answer['answer'])
            my_answer.append(answer)
        return my_answer
    
    def __get_result_by_id(self,q_id,result_lst):
        '''
        根据问题id在提交的试卷结果
        '''
        for result in result_lst:
            if q_id == result['id']:
                return result
        return None
    
    def update_all_answer(self,exam_result):
        '''
        根据考试结果更新所有的答案库self.all_answers
        缩减答案范围
        '''
        # new_answer = []
        result_lst = []
        for key in ['judge_list','single_list','multi_list']:
            result_lst += exam_result[key]
        for answer in self.all_answers:
            q_result = self.__get_result_by_id(answer['id'],result_lst)
            if q_result is None:
                continue
            if q_result['result'] == 1 or q_result['result'] == '1':
                #答对了 保留正确答案
                answer['answer'] = [q_result['my_answer']]
            else:
                #答错了 删除错误答案
                answer['answer'].remove(q_result['my_answer'])
            # new_answer.append(answer['answer'])
        # self.all_answers = new_answer
  
if __name__ == "__main__":
    # r_cookies = login()
    cookies = get_cookie_dict()
    session = requests.session()
    study_course = ChrtCourseInfo(session,headers,cookies)
    # study_course.study_all_courses()
    study_course.pass_all_exam()
